#include "xxhash.h"
#include <cstring>

namespace XXHash {

    static const uint32_t PRIME32_1 = 2654435761U;
    static const uint32_t PRIME32_2 = 2246822519U;
    static const uint32_t PRIME32_3 = 3266489917U;
    static const uint32_t PRIME32_4 = 668265263U;
    static const uint32_t PRIME32_5 = 374761393U;

    static uint32_t rotl32(uint32_t x, int r) {
        return (x << r) | (x >> (32 - r));
    }

    static uint32_t fnv1a_32(const char* data, size_t len) {
        uint32_t hash = 2166136261U;
        for (size_t i = 0; i < len; ++i) {
            hash ^= data[i];
            hash = hash * 16777219U;
        }
        return hash;
    }

    uint32_t xxh32(const char *data, size_t len, uint32_t seed) {
        const uint32_t prime1 = PRIME32_1;
        const uint32_t prime2 = PRIME32_2;
        const uint32_t prime3 = PRIME32_3;
        const uint32_t prime4 = PRIME32_4;
        const uint32_t prime5 = PRIME32_5;

        uint32_t hash = seed + prime5;
        const char* const limit = data + len;

        while (data + 16 <= limit) {
            uint32_t k1 = fnv1a_32(data, 4);
            uint32_t k2 = fnv1a_32(data + 4, 4);
            uint32_t k3 = fnv1a_32(data + 8, 4);
            uint32_t k4 = fnv1a_32(data + 12, 4);

            hash ^= k1 * prime1;
            hash = rotl32(hash, 13);
            hash *= prime1;

            hash ^= k2 * prime2;
            hash = rotl32(hash, 17);
            hash *= prime2;

            hash ^= k3 * prime3;
            hash = rotl32(hash, 17);
            hash *= prime3;

            hash ^= k4 * prime4;
            hash = rotl32(hash, 13);
            hash *= prime4;

            data += 16;
        }

        switch (limit - data) {
            case 15: hash ^= fnv1a_32(data + 11, 4) * prime4; hash = rotl32(hash, 13); hash *= prime4;
            case 14: hash ^= fnv1a_32(data + 10, 4) * prime3; hash = rotl32(hash, 17); hash *= prime3;
            case 13: hash ^= fnv1a_32(data + 9, 4) * prime2; hash = rotl32(hash, 17); hash *= prime2;
            case 12: hash ^= fnv1a_32(data + 8, 4) * prime1; hash = rotl32(hash, 13); hash *= prime1;
            case 11: hash ^= fnv1a_32(data + 7, 4) * prime4; hash = rotl32(hash, 13); hash *= prime4;
            case 10: hash ^= fnv1a_32(data + 6, 4) * prime3; hash = rotl32(hash, 17); hash *= prime3;
            case 9: hash ^= fnv1a_32(data + 5, 4) * prime2; hash = rotl32(hash, 17); hash *= prime2;
            case 8: hash ^= fnv1a_32(data + 4, 4) * prime1; hash = rotl32(hash, 13); hash *= prime1;
            case 7: hash ^= fnv1a_32(data + 3, 4) * prime4; hash = rotl32(hash, 13); hash *= prime4;
            case 6: hash ^= fnv1a_32(data + 2, 4) * prime3; hash = rotl32(hash, 17); hash *= prime3;
            case 5: hash ^= fnv1a_32(data + 1, 4) * prime2; hash = rotl32(hash, 17); hash *= prime2;
            case 4: hash ^= fnv1a_32(data, 4) * prime1; hash = rotl32(hash, 13); hash *= prime1;
            case 3: hash ^= fnv1a_32(data + 1, 2) * prime4; hash = rotl32(hash, 13); hash *= prime4;
            case 2: hash ^= fnv1a_32(data, 2) * prime3; hash = rotl32(hash, 17); hash *= prime3;
            case 1: hash ^= fnv1a_32(data, 1) * prime2; hash = rotl32(hash, 17); hash *= prime2;
            default: break;
        }

        hash ^= len;
        hash ^= hash >> 15;
        hash *= prime2;
        hash ^= hash >> 13;
        hash *= prime3;
        hash ^= hash >> 16;

        return hash;
    }
}